package org.videopractice.common;

import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;

import org.videopractice.SummerApp;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import static android.os.Environment.MEDIA_MOUNTED;

/**
 * 文件管理器，APP内所有文件缓存，地址管理都应该由该工具管理<p/>
 * android 为每个级别都提供了一个 数据空间 和 缓存空间 是为了方便管理和整洁，本质上文件系统并无区别，是有linux分区决定的。
 * 约定 数据空间 应该存放长期使用的，这样手动指定清理不需要的临时缓存不会删除到这部分。
 * 约定 缓存空间 应该存放临时缓存，可以指定清除，区分数据和缓存不会伤及无辜。
 * android 系统提供的在应用设置里的 清除缓存 和 清除数据 操作，并非这里的缓存和数据，而是清理内存缓存 和 清理所有内部空间数据（包括 数据 和 缓存）
 */
public final class FileManager {

    /**
     * 应用内部数据空间（android为了管理区分数据和缓存，数据应该存放长期不清理的数据，方便程序员指定清理数据时方便操作，在系统含义上并无区别）:<p/>
     * 与系统和APP应用本身在一个存储空间资源有限应该尽力避免(类似PC系统盘)<br/>
     * APP内部可见,卸载时会被清理,无需授权可以使用;<br/>
     * 路径 /data/data/包名/files .<br/>
     */
    private static final File APP_INNER_STORAGE_DATA = SummerApp.summerApp().getFilesDir();

    /**
     * 应用内部缓存空间:<p/>
     * 与系统和APP应用本身在一个存储空间资源有限应该尽力避免(类似PC系统盘)<br/>
     * APP内部可见,卸载时会被清理,无需授权可以使用;<br/>
     * 路径 /data/data/包名/cache .<br/>
     */
    private static final File APP_INNER_STORAGE_CACHE = SummerApp.summerApp().getCacheDir();

    /**
     * 应用对外数据空间(android为APP应用在自带外部存储空间上分配的):<p/>
     * 手机自带存储中的外部存储空间，区别于系统和APP应用文件存储空间，(类似PC的非系统分区)
     * APP内部和非root用户都可见,卸载时会被清理,app内无需授权可以使用;<br/>
     * 路径 /storage/emulated/0/Android/data/包名/files/* <br/>
     */
    private static final File APP_OUTER_STORAGE_DATA = SummerApp.summerApp().getExternalFilesDir(Environment.DIRECTORY_PICTURES);

    /**
     * 应用对外缓存空间(android为APP应用在自带外部存储空间上分配的):<p/>
     * 手机自带存储中的外部存储空间，区别于系统和APP应用文件存储空间，(类似PC的非系统分区)
     * APP内部和非root用户都可见,卸载时会被清理,app内无需授权可以使用;<br/>
     * 路径 /storage/emulated/0/Android/data/包名/cache <br/>
     */
    private static final File APP_OUTER_STORAGE_CACHE = SummerApp.summerApp().getExternalCacheDir();

    /**
     * 外部公开空间<p/>
     * 外部存储空间区别于内部空间没有权限(类似PC的非系统分区)<br/>
     * 额外扩展的card空间也在这里。使用随意，与下面public相比这里算是一个根目录，而public事android系统实现命名好的文件夹。<br/>
     * 非root用户都可见,卸载时不会被清理,app需授权可以使用;<br/>
     * 路径 /storage/emulated/0 <br/>
     */
    private static final File OUTER_STORAGE_DATA = Environment.getExternalStorageDirectory();

    /**
     * 外部公开共享空间<p/>
     * 外部存储空间区别于内部空间没有权限(类似PC的非系统分区)<br/>
     * 额外扩展的card空间也在这里。这里的开放共享 public 是指系统默认提供了几个文件夹，所有APP存放文件都会更具类型进入指定文件夹。例如图片 和 下载。<br/>
     * 非root用户都可见,卸载时不会被清理,app需授权可以使用;<br/>
     * 路径 /storage/emulated/0/* <br/>
     */
    private static final File OUTER_STORAGE_PUBLIC = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

    private static final String DEF_DIR = "qiremanhua";

    //region    #文件管理 begin

    /**
     * 根据 优先使用应用外部存储原则 分配一个数据文件目录，这里返回的都是应用存储空间不需要授权
     * @param dir 分配文件地址的目录
     * @return 一个可用的文件地址，如果外部存储可用分配外存，如果外部存储不可用再使用内部存储
     */
    private static File allotDataFile(String dir) {
        File directory;

        // 判断外部存储是否可用
        if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            // 可用使用外部存储
            directory = SummerApp.summerApp().getExternalFilesDir(dir);
        } else {
            // 没外部存储就使用内部存储
            directory = new File(APP_INNER_STORAGE_DATA, dir);
        }

        //判断文件目录是否存在
        if(!directory.exists()) {
            directory.mkdirs();
        }

        return directory;
    }

    /**
     * 根据 优先使用应用外部存储原则 分配一个缓存文件目录，这里返回的都是应用存储空间不需要授权
     * @param dir 分配文件地址的目录
     * @return 一个可用的文件地址，如果外部存储可用分配外存，如果外部存储不可用再使用内部存储
     */
    private static File allotCacheFile(String dir) {
        File directory;

        // 判断外部存储是否可用
        if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            // 可用使用外部存储
            directory = new File(APP_OUTER_STORAGE_CACHE, dir);
        } else {
            // 没外部存储就使用内部存储
            directory = new File(APP_INNER_STORAGE_CACHE, dir);
        }

        //判断文件目录是否存在
        if(!directory.exists()) {
            directory.mkdirs();
        }

        return directory;
    }

    /**
     * 随机构建一个公共数据存储空间缓存文件，在外部公共空间创建文件注意需要事先授权才可使用，
     * @return 文件格式：QR_JPEG_ yyyy_MM_dd_HHmmss _ 缓存文件随机字串(系统缓存生成) .jpg
     */
    public static File randomBuildOuterFile() {
        String fileName = "QR_JPEG_" + System.currentTimeMillis() + "_";
        try {
            return File.createTempFile(fileName, ".jpg", OUTER_STORAGE_DATA);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 构建一个公共数据存储空间缓存文件，在外部公共空间创建文件注意需要事先授权才可使用
     * @return
     */
    public static File buildOuterFile(String fileName) {
        return new File(OUTER_STORAGE_DATA, fileName);
    }

    /**
     * 根据 漫画ID 作为一级目录， 章节ID 作为2级目录 在应用数据空间上分配一个目录用于存放章节内容图片
     * @param comicId 漫画ID
     * @param chapterId 章节ID
     * @return 章节目录, 并未做创建判断，使用前应该视具体情况选择构建
     */
    public static File allotChapterFolder(int comicId, int chapterId) {
        File rootDir = allotDataFile(String.valueOf(comicId));
        File chapterDir = new File(rootDir, String.valueOf(chapterId));
        return chapterDir;
    }

    /**
     * 通过URL解析文件名
     * @param url url地址
     * @return 得到最后一个 / 到 最后一个？ 之间的字串，如果？不存在则到结束
     */
    public static String parseFileNameBy(String url) {
        String filename = null;
        String[] strings = url.split("/");
        for (String string : strings) {
            if (string.contains("?")) {
                int endIndex = string.indexOf("?");
                if (endIndex != -1) {
                    filename = string.substring(0, endIndex);
                    return filename;
                }
            }
        }
        if (strings.length > 0) {
            filename = strings[strings.length - 1];
        }
        return filename;
    }

    //endregion #文件管理 end

    //region    #文件操作 begin

    /**
     * 统计文件夹的总大小，这是个递归方法会遍历所有子文件与文件夹, O(n)
     * @param folder 文件夹目录
     * @return 文件夹总大小。
     */
    private static long countFolderSize(File folder) {
        long size = 0;
        File[] subList = folder.listFiles();
        for(File sub : subList) {
            if(sub.isDirectory()) {
                size += countFolderSize(sub);
            } else {
                size += sub.length();
            }
        }
        return size;
    }

    /**
     * 检查文件是否存在
     * @param file
     * @return file 不存在或为 null 返回 false 否则返回 true
     */
    public static boolean exists(File file) {
        return file != null && file.exists();
    }

    /**
     * 检查文件是否可读
     * @param file 目标文件
     * @return 文件为 null 或 不存在 或 不可读 都返回 false , 否则返回 true
     */
    public static boolean checkFileCanRead(File file) {
        return exists(file) && file.canRead();
    }

    /**
     * 检查图片文件是否有效
     * @param bitmapFile 文件引用
     * @return true 为有效， false 为无效
     */
    public static boolean checkBitmapFileIsValid(File bitmapFile) {
        return exists(bitmapFile) && checkBitmapFileIsValid(bitmapFile.getAbsolutePath());
    }

    /**
     * 检查图片文件是否有效，指定路径的目标文件必须存在，否则会抛出异常
     * @param bitmapFilePath 文件完整路径
     * @return true 为有效， false 为无效
     */
    public static boolean checkBitmapFileIsValid(String bitmapFilePath) {
        BitmapFactory.Options options = null;
        if (options == null) {
            options = new BitmapFactory.Options();
        }
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFile(bitmapFilePath, options);
        if (options.mCancel || options.outWidth == -1 || options.outHeight == -1) {
            return false;
        }
        return true;
    }

    /**
     * 删除目录，会遍历目录的目录进行删除。O(N)
     * @param folder 指定目录 file
     */
    private synchronized static void deleteDirectory(File folder) {
        if (folder.exists()) {
            File[] files = folder.listFiles();
            if (files == null) {
                return;
            }
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    deleteDirectory(files[i]);
                } else {
                    files[i].delete();
                }
            }
            folder.delete();
        }
    }

    //endregion #文件操作 end

    //region    #拷贝文件 begin

    public static File copy(File srcFile, File dstFile) {
        try {
            int byteSum = 0, byteRead = 0;
            if (srcFile.exists()) { //文件存在时
                InputStream inStream = new FileInputStream(srcFile); //读入原文件
                FileOutputStream fos = new FileOutputStream(dstFile);
                byte[] buffer = new byte[1444];
                while ((byteRead = inStream.read(buffer)) != -1) {
                    byteSum += byteRead; //字节数 文件大小
                    fos.write(buffer, 0, byteRead);
                    Logger.d("复制中：已累计赋值大小 = " + byteSum);
                }
                fos.flush();
                fos.close();
                inStream.close();
            }
        } catch (Exception e) {
            System.out.println("复制单个文件操作出错");
            e.printStackTrace();
        }
        return dstFile;
    }

    //endregion #拷贝文件 end

    //region    #图册库操作 begin

    /**
     * 更新系统库图，再相册选择时得已查看到最新的数据。
     * @param bitmapFile 图片文件
     * @param fileName 图片文件名
     */
    private static void updateSystemGallery(File bitmapFile, String fileName) {
        // 其次把文件插入到系统图库
        try {
            MediaStore.Images.Media.insertImage(SummerApp.summerApp().getContentResolver(), bitmapFile.getAbsolutePath(), fileName, null);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 最后通知图库更新
        SummerApp.summerApp().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(bitmapFile.getAbsolutePath())));
    }

    /////////////////////////////////////////////保存///////////////////////////////////////////////////////////

    /**
     * 参见 {@link #saveToSystemGallery(Bitmap, String, String, CompressFormat, int) saveToSystemGallery}
     */
    public static File saveToSystemGallery(Bitmap bitmap, String dstFileName) {
        return saveToSystemGallery(bitmap, dstFileName, 100);
    }

    /**
     * 参见 {@link #saveToSystemGallery(Bitmap, String, String, CompressFormat, int) saveToSystemGallery}
     */
    public static File saveToSystemGallery(Bitmap bitmap, String dstFileName, int quality) {
        return saveToSystemGallery(bitmap, DEF_DIR, dstFileName, CompressFormat.JPEG, quality);
    }

    /**
     * 参见 {@link #saveToSystemGallery(Bitmap, String, String, CompressFormat, int) saveToSystemGallery}
     */
    public static File saveToSystemGallery(Bitmap bitmap, String dstFileName, CompressFormat format, int quality) {
        return saveToSystemGallery(bitmap, DEF_DIR, dstFileName, format, quality);
    }

    /**
     * 保存图片到相册并刷新相册，再调用前需要做动态权限获取，否则无法访问到外部存储。<p/>
     * PS.本来期望做成授权使用的，再这里做权限获取，那样就可以不用在意权限的问题，统一使用这里的方法，自然就会判断权限了。但不符合职能单一原则
     * @param bitmap 目标图片
     * @param dstDirectory 目标目录
     * @param dstFileName 目标文件名
     * @param format 图片格式
     * @param quality 图片质量
     * @return 保存后的文件
     */
    public static File saveToSystemGallery(Bitmap bitmap, String dstDirectory, String dstFileName, CompressFormat format, int quality) {
        // 首先保存图片
        File dstDir = new File(OUTER_STORAGE_PUBLIC, dstDirectory);
        if (!dstDir.exists()) {
            dstDir.mkdir();
        }
        File dstFile = new File(dstDir, dstFileName);
        saveBitmap(bitmap, dstFile, format, quality);
        updateSystemGallery(dstFile, dstFileName);

        return dstFile;
    }

    /////////////////////////////////////////////拷贝///////////////////////////////////////////////////////////

    /**
     * 参见 {@link #copyToSystemGallery(File, String, String) copyToSystemGallery}
     */
    public static void copyToSystemGallery(File bmpFile) {
        copyToSystemGallery(bmpFile, bmpFile.getName());
    }

    /**
     * 参见 {@link #copyToSystemGallery(File, String, String) copyToSystemGallery}
     */
    public static File copyToSystemGallery(File bitmapFile, String dstFileName) {
        return copyToSystemGallery(bitmapFile, DEF_DIR, dstFileName);
    }

    /**
     * 拷贝图片文件到相册并刷新相册，再调用前需要做动态权限获取，否则无法访问到外部存储。<p/>
     * PS.本来期望做成授权使用的，再这里做权限获取，那样就可以不用在意权限的问题，统一使用这里的方法，自然就会判断权限了。但不符合职能单一原则
     * @param bitmapFile 图片源文件
     * @param dstDirectory 目标目录
     * @param dstFileName 目标名称
     * @return
     */
    public static File copyToSystemGallery(File bitmapFile, String dstDirectory, String dstFileName) {
        // 首先保存图片
        File dstDir = new File(OUTER_STORAGE_PUBLIC, dstDirectory);
        if (!dstDir.exists()) {
            dstDir.mkdir();
        }
        File dstFile = new File(dstDir, dstFileName);
        updateSystemGallery(copy(bitmapFile, dstFile), dstFileName);
        return dstFile;
    }

    //endregion #图册库操作 end

    //region    #保存图片 begin

    /**
     * @see #saveToDataStorage(Bitmap, String, String)
     */
    public static void saveToDataStorage(Bitmap bitmap, String fileName) {
        saveToDataStorage(bitmap, "", fileName);
    }

    /**
     * 保存图片到数据存储空间，使用优先级(应用外部数据空间 > 应用内部数据空间)，应用内无需授权。
     * 数据空间，存储长期使用文件应该放在这里。
     * @param bitmap 源图片
     * @param dir 目录
     * @param fileName 文件名
     */
    public static void saveToDataStorage(Bitmap bitmap, String dir, String fileName) {
        File file = new File(allotDataFile(dir), fileName);
        saveBitmap(bitmap, file);
    }

    /**
     * @see #saveToCacheStorage(Bitmap, String, String)
     */
    public static void saveToCacheStorage(Bitmap bitmap, String fileName) {
        saveToCacheStorage(bitmap, "", fileName);
    }

    /**
     * 保存图片到缓存存储空间，使用优先级(应用外部数据空间 > 应用内部数据空间)，应用内无需授权。
     * 缓存空间，临时使用文件应该放在这里
     * @param bitmap 源图片
     * @param dir 目录
     * @param fileName 文件名
     */
    public static void saveToCacheStorage(Bitmap bitmap, String dir, String fileName) {
        File file = new File(allotCacheFile(dir), fileName);
        saveBitmap(bitmap, file);
    }

    /**
     * @see #saveBitmap(Bitmap, File, CompressFormat, int)
     */
    public static void saveBitmap(Bitmap bitmap, File file) {
        saveBitmap(bitmap, file, CompressFormat.JPEG);
    }
    
    /**
     * @see #saveBitmap(Bitmap, File, CompressFormat, int)
     */
    public static void saveBitmap(Bitmap bitmap, File file, int quality) {
        saveBitmap(bitmap, file, CompressFormat.JPEG, quality);
    }

    /**
     * @see #saveBitmap(Bitmap, File, CompressFormat, int)
     */
    public static void saveBitmap(Bitmap bitmap, File file, CompressFormat format) {
        saveBitmap(bitmap, file, format, 100);
    }

    /**
     * 保存图片到指定文件
     * @param bitmap 源图片
     * @param file 目标文件
     * @param format 图片格式
     * @param quality 图片质量
     */
    public static void saveBitmap(Bitmap bitmap, File file, CompressFormat format, int quality) {
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bitmap.compress(format, quality, fos);
            fos.flush();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //endregion #保存图片 end

    //region    #文件讀取 begin

    /**
     * 读取 Assets 下的文件内容
     * @param fileName 以 Assets 目录为根的相对路径名
     * @param def 默认值，如果读取失败将返回这个
     * @return 如果读取成功返回具体文件内容，如果失败返回 {@code def} 内容
     */
    public static String readAssetsFileContent(String fileName, String def) {
        try {
            InputStream inputStream = SummerApp.summerApp().getResources().getAssets().open(fileName);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            StringBuilder stringBuilder = new StringBuilder();
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            return stringBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return def;
    }

    //endregion #文件讀取 end
}
